#ifndef ZXC_RP_BLINN_PHONG
#define ZXC_RP_BLINN_PHONG

// 实现Blinn-Phong着色

#include "Common.hlsl"

#define MAX_LIGHTS 256

float4 mainLightDirection;
float4 mainLightColor;
float4 additionalLightCount;

cbuffer AdditionalLight
{
    float4 additionalLightPositions[MAX_LIGHTS];
    float4 additionalLightDirections[MAX_LIGHTS];
    float4 additionalLightColors[MAX_LIGHTS];
    float4 additionalLightRanges[MAX_LIGHTS];
    float4 additionalLightSpots[MAX_LIGHTS];
}

sampler2D _MainTex;

cbuffer UnityPerDraw
{
    float3 _Ambient;
    float3 _Specular;
}

struct Attributes
{
    float3 positionOS : POSITION;
    float3 normalOS : NORMAL;
    float2 uv : TEXCOORD;
};

struct Varyings
{
    float4 positionCS : SV_POSITION;
    float2 uv : TEXCOORD0;
    float3 positionWS : TEXCOORD1;
    float3 normalWS : TEXCOORD2;
};

struct Light
{
    float3 color;
    float3 direction;
};

// 光照距离衰减
// (r0 / r) ^ 2 的线性衰减，
// 但这个公式在r特别小的时候会导致亮度过高
// r太大的时候，函数值取不到0，
// 因此一般会用一些改进的算法
float GetAtteDistance(float disSqr, float2 rangeSqr)
{
    // 这里衰减分两部分，一部分是平方衰减，1 / r^2
    // 之后需要乘一个window函数，用于将平方衰减函数在某个距离上取到0，并且
    // 一阶导数为0
    // 这里window函数使用和方向衰减一样的线性过渡函数(从80%开始过渡）
    float lightAtte = rcp(disSqr);
    // float fadeDistance = 0.8 * 0.8 * rangeSqr;
    // float smoothFactor = (rangeSqr - disSqr) / (rangeSqr - fadeDistance);
    // 上面这个可以改写为MAD指令
    float smoothFactor = saturate(disSqr * rangeSqr.x + rangeSqr.y);
    return lightAtte * smoothFactor;
}

// 光照方向衰减
float GetAtteDirection(float cosS, float2 dirAtte)
{
    // (cosS - cosU) / (cosP - cosU)
    // 通用可以搞成MAD指令
    return saturate(cosS * dirAtte.x + dirAtte.y);
}

Light GetMainLight(float3 positionWS)
{
    Light light;
    light.color = mainLightColor.xyz;
    light.direction = mainLightDirection.xyz;
    return light;
}

Light GetAdditionalLight(uint index, float3 positionWS)
{
    float3 lp = additionalLightPositions[index].xyz;
    float3 l = lp - positionWS;
    float disSqr = dot(l, l);
    l = l / sqrt(disSqr);
    float atteDistance = GetAtteDistance(disSqr, additionalLightRanges[index].zw);
    float cosS = dot(-additionalLightDirections[index].xyz, -l);
    float atteDirection = GetAtteDirection(cosS, additionalLightSpots[index].xy);
    Light light;
    light.direction = l;
    light.color = additionalLightColors[index].rgb * atteDistance * atteDirection;
    return light;
}

float3 GetDiffuse(float3 n, Light light, float3 color)
{
    return saturate(dot(n, light.direction)) * light.color * color;
}

float3 GetSpecular(float3 n, float3 v, Light light)
{
    float3 h = normalize(light.direction + v);
    return pow(saturate(dot(h, n)), 60) * light.color * _Specular;
}
            
Varyings Vert(Attributes input)
{
    Varyings output;
    output.positionCS = ObjectToHClip(input.positionOS);
    output.positionWS = ObjectToWorld(input.positionOS);
    output.normalWS = ObjectToWorldNormal(input.normalOS);
    output.uv = input.uv;
    return output;
}

float4 Frag(Varyings input) : SV_TARGET
{
    float3 color = tex2D(_MainTex, input.uv).rgb;
    float3 n = normalize(input.normalWS);
    float3 v = normalize(cameraPos.xyz - input.positionWS);

    // ambient
    float3 ambient = _Ambient;
    
    // main light
    Light mainLight = GetMainLight(input.positionWS);
    // diffuse
    float3 diffuse = GetDiffuse(n, mainLight, color);
    // specular
    float3 specular = GetSpecular(n, v, mainLight);

    // additional light
    uint count = min((uint)additionalLightCount.x, MAX_LIGHTS);
    for(uint i = 0; i < count; ++i)
    {
        Light light = GetAdditionalLight(i, input.positionWS);
        diffuse += GetDiffuse(n, light, color);
        specular += GetSpecular(n, v, light);
    }
    
    return float4(ambient + diffuse + specular, 1.0);
}

#endif